﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BGSpaceShooter;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace BGSpaceShooterServer
{
    internal class GameStateComputer
    {
        #region Movement

        public static Keys Forward = Keys.W;
        public static Keys Backward = Keys.S;
        public static Keys Left = Keys.Left;
        public static Keys Right = Keys.Right;
        public static Keys RollLeft = Keys.A;
        public static Keys RollRight = Keys.D;
        public static Keys Down = Keys.Up;
        public static Keys Up = Keys.Down;
        public static Keys Reset = Keys.U;

        private Matrix GetRotationMatrix(Player player)
        {
            return player.StarFighter.Rotation;
        }

        /// <summary>
        /// Oblicza zmiany położenia i prędkości gracza od ostatniego update'u
        /// </summary>
        /// <param name="player">Gracz, którego ruch ma zostać obliczony</param>
        public void CalculateMove(Player player)
        {
            DateTime now = DateTime.Now;
            TimeSpan time = now - player.LastUpdate;
            player.StarFighter.ResetStarFighterState();

            CalculateSlowDown(time, player);

            CalculateForwardMove(time, player);

            CalculateXAxisMovement(time, player);

            CalculateZAxisMovement(time, player);

            player.StarFighter.Position += player.StarFighter.Velocity * Convert.ToSingle(time.TotalMilliseconds);
            Vector3 angle = player.StarFighter.RotationVel * Convert.ToSingle(time.TotalMilliseconds);
            player.StarFighter.Rotation *= Matrix.CreateFromYawPitchRoll(angle.Y, angle.X, angle.Z);

            player.LastUpdate = now;

            if (player.LastInput.Contains(Reset))
            {
                player.Reset();
                player.Points--;
            }
        }

        private void CalculateForwardMove(TimeSpan time, Player player)
        {
            float forwardAcc = 0;
            if (player.LastInput.Contains(Forward))
            {
                forwardAcc -= player.StarFighter.Acceleration.X * player.StarFighter.GetComponentByName("Engines").HealthPercent;
                if (player.StarFighter.GetComponentByName("Engines").HealthPercent > 0)
                    player.StarFighter.WorkingEngines.Add(EngineName.Main);

            }
            if (player.LastInput.Contains(Backward))
            {
                forwardAcc += player.StarFighter.Acceleration.Y * player.StarFighter.GetComponentByName("Bow").HealthPercent;
            }
            Vector3 aForward = new Vector3(0, 0, forwardAcc);

            aForward = Vector3.Transform(aForward, GetRotationMatrix(player));

            Vector3 forwardVel = player.StarFighter.Velocity + aForward * Convert.ToSingle(time.TotalMilliseconds);

            if ((forwardAcc < 0 && forwardVel.Length() > player.StarFighter.MaxVelocity.X &&
                    player.StarFighter.Velocity.Length() <= player.StarFighter.MaxVelocity.X) ||
                (forwardAcc >= 0 && forwardVel.Length() > player.StarFighter.MaxVelocity.Y &&
                    player.StarFighter.Velocity.Length() <= player.StarFighter.MaxVelocity.Y))
            {
                aForward = Vector3.Zero;
            }

            player.StarFighter.Position += 0.5f * aForward * Convert.ToSingle(Math.Pow(time.TotalMilliseconds, 2));

            player.StarFighter.Velocity += aForward * Convert.ToSingle(time.TotalMilliseconds);

            float forwardRot = 0;
            if (player.LastInput.Contains(Left))
            {
                forwardRot -= forwardAcc;
            }
            if (player.LastInput.Contains(Right))
            {
                forwardRot += forwardAcc;
            }

            CalculateRotation(time, player, 0.1f * forwardRot, new Vector3(0, 1.0f, 0));
        }

        private void CalculateSlowDown(TimeSpan time, Player player)
        {
            if (player.StarFighter.Velocity != Vector3.Zero)
            {
                Vector3 slowDown = -Vector3.Normalize(player.StarFighter.Velocity) * player.StarFighter.MaxVelocity.X *
                                   0.003f;

                if(player.LastInput.Contains(Backward))
                {
                    slowDown *= 2;
                }

                if (slowDown.Length() > player.StarFighter.Velocity.Length())
                {
                    slowDown = -player.StarFighter.Velocity;
                }
                player.StarFighter.Velocity += slowDown;
            }
            if (player.StarFighter.RotationVel != Vector3.Zero)
            {
                Vector3 slowDown = -Vector3.Normalize(player.StarFighter.RotationVel) * player.StarFighter.MaxVelocity.Z *
                                   0.02f;

                if (player.LastInput.Contains(Backward))
                {
                    slowDown *= 2;
                }

                if (slowDown.Length() > player.StarFighter.RotationVel.Length())
                {
                    slowDown = -player.StarFighter.RotationVel;
                }
                player.StarFighter.RotationVel += slowDown;
            }
        }

        private void CalculateUP(TimeSpan time, Player player, float upAcc, float enginesCnt)
        {
            Vector3 aUp = new Vector3(0, upAcc, 0);
            aUp = Vector3.Transform(aUp, GetRotationMatrix(player));

            Vector3 upVel = player.StarFighter.Velocity + aUp * Convert.ToSingle(time.TotalMilliseconds);

            if (upVel.Length() > enginesCnt * player.StarFighter.MaxVelocity.Y)
            {
                aUp = Vector3.Zero;
            }

            player.StarFighter.Position += 0.5f * aUp * Convert.ToSingle(Math.Pow(time.TotalMilliseconds, 2));

            player.StarFighter.Velocity += aUp * Convert.ToSingle(time.TotalMilliseconds);
        }

        private void CalculateRotation(TimeSpan time, Player player, float rotAcc, Vector3 axisMask)
        {
            Vector3 xRot = new Vector3(rotAcc * axisMask.X, rotAcc * axisMask.Y, rotAcc * axisMask.Z);

            xRot = Vector3.Transform(xRot, GetRotationMatrix(player));

            Vector3 xrVel = player.StarFighter.RotationVel + xRot * Convert.ToSingle(time.TotalMilliseconds);

            if (xrVel.Length() > player.StarFighter.MaxVelocity.Z)
            {
                xRot = Vector3.Zero;
            }

            Vector3 angle = 0.5f * xRot * Convert.ToSingle(Math.Pow(time.TotalMilliseconds, 2));
            player.StarFighter.Rotation *= Matrix.CreateFromYawPitchRoll(angle.Y, angle.X, angle.Z);

            player.StarFighter.RotationVel += xRot * Convert.ToSingle(time.TotalMilliseconds);

        }

        private void CalculateXAxisMovement(TimeSpan time, Player player)
        {
            float upAcc = 0;

            if (player.LastInput.Contains(Down))
            {
                upAcc += player.StarFighter.Acceleration.Y *
                           player.StarFighter.GetComponentByName("Bottom").HealthPercent;
                if (player.StarFighter.GetComponentByName("Bottom").HealthPercent > 0)
                    player.StarFighter.WorkingEngines.Add(EngineName.topX);
            }
            if (player.LastInput.Contains(Up))
            {
                upAcc += player.StarFighter.Acceleration.Y *
                           player.StarFighter.GetComponentByName("Bow").HealthPercent;
                if (player.StarFighter.GetComponentByName("Bow").HealthPercent > 0)
                    player.StarFighter.WorkingEngines.Add(EngineName.bottomX);
            }

            CalculateUP(time, player, upAcc,
                        player.StarFighter.GetComponentByName("Bottom").HealthPercent +
                        player.StarFighter.GetComponentByName("Bow").HealthPercent);

            float xrotAcc = 0;

            if (player.LastInput.Contains(Down))
            {
                xrotAcc -= player.StarFighter.Acceleration.Z * player.StarFighter.GetComponentByName("Bottom").HealthPercent;
            }
            if (player.LastInput.Contains(Up))
            {
                xrotAcc += player.StarFighter.Acceleration.Z * player.StarFighter.GetComponentByName("Bow").HealthPercent;
            }

            CalculateRotation(time, player, xrotAcc, new Vector3(1.0f, 0, 0));
        }

        private void CalculateZAxisMovement(TimeSpan time, Player player)
        {
            float upAcc = 0;

            if (player.LastInput.Contains(RollLeft))
            {
                upAcc += player.StarFighter.Acceleration.Y *
                           player.StarFighter.GetComponentByName("WingRight0").HealthPercent;
                if (player.StarFighter.GetComponentByName("WingRight0").HealthPercent > 0)
                    player.StarFighter.WorkingEngines.Add(EngineName.rightZ);
            }
            if (player.LastInput.Contains(RollRight))
            {
                upAcc += player.StarFighter.Acceleration.Y *
                           player.StarFighter.GetComponentByName("WingLeft0").HealthPercent;
                if (player.StarFighter.GetComponentByName("WingRight0").HealthPercent > 0)
                    player.StarFighter.WorkingEngines.Add(EngineName.leftZ);
            }

            CalculateUP(time, player, upAcc,
                        player.StarFighter.GetComponentByName("WingLeft0").HealthPercent +
                        player.StarFighter.GetComponentByName("WingRight0").HealthPercent);

            float zrotAcc = 0;

            if (player.LastInput.Contains(RollLeft))
            {
                zrotAcc += player.StarFighter.Acceleration.Z * player.StarFighter.GetComponentByName("WingRight0").HealthPercent;
            }
            if (player.LastInput.Contains(RollRight))
            {
                zrotAcc -= player.StarFighter.Acceleration.Z * player.StarFighter.GetComponentByName("WingLeft0").HealthPercent;
            }

            CalculateRotation(time, player, zrotAcc, new Vector3(0, 0, 1.0f));
        }

        #endregion

        #region Collisions

        /// <summary>
        /// Ilosc probek na update wykrywania kolizji
        /// </summary>
        private const int samplesPerUpdate = 10;

        /// <summary>
        /// Siła odrzutu po wybuchu
        /// </summary>
        private const float explosionStrength = 0.05f;

        /// <summary>
        /// Oblicza kolizje między graczami a graczami, graczami a pociskami oraz pociskami a pociskami
        /// </summary>
        /// <param name="players">Lista graczy</param>
        /// <param name="projectiles">Lista pocisków</param>
        public void CalculateCollisions(IEnumerable<Player> players, IEnumerable<Projectile> projectiles)
        {
            for (int i = 0; i < players.Count(); ++i)
            {
                Player player = players.ElementAt(i);
                for (int j = i + 1; j < players.Count(); j++)
                {
                    CalculateCollision(player.StarFighter, players.ElementAt(j).StarFighter);
                }
                foreach (Projectile projectile in projectiles)
                {
                    if (player.PlayerID != projectile.PlayerId)
                    {
                        CalculateCollision(player.StarFighter, projectile);
                    }
                }
            }
            for (int i = 0; i < projectiles.Count() - 1; i++)
            {
                for (int j = i + 1; j < projectiles.Count(); j++)
                {
                    CalculateCollision(projectiles.ElementAt(i), projectiles.ElementAt(j));
                }
            }
        }

        private void CalculateCollision(ICollidable obj1, ICollidable obj2)
        {
            float minDist = obj1.BoundingSphere.Radius + obj2.BoundingSphere.Radius;

            if (obj2 is Projectile && ((Projectile)obj2).ProjectileType == GameObjectName.Bullet)
            {
                bool collided = false;
                for (int i = 0; i < samplesPerUpdate && !collided; ++i)
                {
                    Vector3 distance = obj2.Position -
                                       ((Projectile)obj2).LastPositionDelta * Convert.ToSingle(samplesPerUpdate - i - 1) /
                                       Convert.ToSingle(samplesPerUpdate) - obj1.Position;

                    if (Math.Abs(distance.X) <= minDist && Math.Abs(distance.Y) <= minDist && Math.Abs(distance.Z) <= minDist && distance.Length() <= minDist)
                    {
                        collided = true;
                    }
                }
                if (!collided)
                {
                    return;
                }
            }
            else
            {
                Vector3 distance = obj2.Position - obj1.Position;

                if (Math.Abs(distance.X) > minDist || Math.Abs(distance.Y) > minDist || Math.Abs(distance.Z) > minDist)
                {
                    return;
                }
                if (distance.Length() > minDist)
                {
                    return;
                }
            }
            //mamy kolizje!
            if (HandleCollision(obj1, obj2))
            {
                if (!HandleCollision(obj2, obj1) && obj2 is StarFighter)
                {
                    StarFighter fighter = obj2 as StarFighter;
                    --fighter.HP;
                    CalculateExplosion(fighter, obj1);
                }
            }
        }

        private bool HandleCollision(ICollidable collidable, ICollidable collisionCause)
        {
            if (collidable is Projectile)
            {
                Projectile projectile = collidable as Projectile;
                --projectile.HP;
                if (collisionCause is StarFighter)
                    (collisionCause as StarFighter).LastPlayerIdHit = (collidable as Projectile).PlayerId;
                if (projectile.HP <= 0)
                    return true;
            }
            else if (collidable is StarFighter)
            {
                StarFighter fighter = collidable as StarFighter;

                bool hit = false;
                int iterationCnt = 1;
                if (collisionCause is Projectile && ((Projectile)collisionCause).ProjectileType == GameObjectName.Bullet)
                {
                    iterationCnt = samplesPerUpdate;
                }
                for (int i = 0; i < iterationCnt && !hit; ++i)
                {
                    foreach (StarFighterComponent component in fighter.StarFighterComponents)
                    {
                        hit |= CalculateCollision(collisionCause, component, fighter, i);
                    }
                }
                if (hit)
                {
                    --fighter.HP;
                    CalculateExplosion(fighter, collisionCause);
                }
                return hit;
            }
            return false;
        }

        private bool CalculateCollision(ICollidable collidable, StarFighterComponent component, StarFighter owner, int iteration)
        {
            float minDist = collidable.BoundingSphere.Radius + component.BoundingSphere.Radius;

            Vector3 distance = collidable.Position -
                                       Vector3.Transform(component.BoundingSphere.Center,
                                                         Matrix.CreateRotationX(MathHelper.PiOver2) *
                                                         Matrix.CreateRotationZ(MathHelper.Pi) * owner.Rotation) -
                                       owner.Position;

            if (collidable is Projectile)
            {
                distance -= ((Projectile)collidable).LastPositionDelta *
                            Convert.ToSingle(samplesPerUpdate - iteration - 1) /
                            Convert.ToSingle(samplesPerUpdate);
            }

            if (Math.Abs(distance.X) > minDist || Math.Abs(distance.Y) > minDist ||
                Math.Abs(distance.Z) > minDist)
            {
                return false;
            }
            if (distance.Length() > minDist)
            {
                return false;
            }

            --component.HP;

            return true;
        }

        private void CalculateExplosion(StarFighter fighter, ICollidable collisionCause)
        {
            Vector3 vector = fighter.Position - collisionCause.Position;
            if (vector == Vector3.Zero)
                vector = new Vector3(0, 1, 0);
            vector.Normalize();
            fighter.Velocity += vector * explosionStrength;

            vector = Vector3.Transform(vector, Matrix.Invert(fighter.Rotation));

            vector = new Vector3(Math.Sign(vector.X), Math.Sign(vector.Y), Math.Sign(vector.Z));

            fighter.RotationVel += vector * explosionStrength * 0.04f;
        }

        #endregion
    }
}
